package retrieval;

import static preprocess.XMLContracter.ATTRIBUTE;
import static preprocess.XMLContracter.NESTING;
import static preprocess.XMLContracter.REPETITION;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.SAXReader;

import preprocess.XMLContracter;
import preprocess.XMLDoc;
import similarity.Similarity;
import similarity.SimpleTEDSimilarity;
import similarity.TextSimilarity;
import utils.Configuration;
import classification.calcSimilarity.CalcSimilarityBySLVM;

public class SearchSimiXMLDoc
{
    private static SearchSimiXMLDoc searcher           = null;

    private ArrayList<Similarity>   calcSimilarityList = null;
    private File[]                  files              = null;
    private int                     K                  = 10;

    private static final String     FILE_PATH          = Configuration.RETRIVAL.FilePath;
    public static final int         VSM                = 0;
    public static final int         TED                = 1;
    public static final int         SLVM               = 2;

    public static SearchSimiXMLDoc getInstance()
    {
        if (searcher == null)
        {
            searcher = new SearchSimiXMLDoc();
        }
        return searcher;
    }

    private SearchSimiXMLDoc()
    {
        files = this.loadFiles(FILE_PATH);

        calcSimilarityList = new ArrayList<Similarity>();
        calcSimilarityList.add(new TextSimilarity());
        calcSimilarityList.add(new SimpleTEDSimilarity());
        calcSimilarityList.add(new CalcSimilarityBySLVM());
    }

    public HashMap<String, Document> search(final File xml, final int method)
    {
        HashMap<String, Document> docMap = new HashMap<String, Document>();
        SAXReader reader = new SAXReader();
        XMLContracter con = new XMLContracter();
        try
        {
            /********************** 计算与所有待查找XML的相似度值 ***********************/
            Document doc = reader.read(xml);
            doc = con.contract(doc, NESTING | REPETITION | ATTRIBUTE);
            XMLDoc XMLDoc1 = new XMLDoc(doc, "searcher");

            /* 记录document对象的数组 */
            Document[] docSet = new Document[this.files.length];

            /* 记录文件名的数组 */
            String[] fileNameSet = new String[this.files.length];

            /* 记录和所有文件相似度计算结果的数组 */
            double[] result = new double[this.files.length];

            int index = 0;
            XMLDoc XMLDoc2 = null;
            for (File file : this.files)
            {
                /* 找到文件名（不包括.xml） */
                String fileName = file.getName();
                int partition = fileName.lastIndexOf(".");
                String docID = fileName.substring(0, partition);

                /* 创建当前待计算相似度值的XMLDoc对象 */
                Document doc2 = reader.read(file);
                doc2 = con.contract(doc2, NESTING | REPETITION | ATTRIBUTE);
                docSet[index] = doc2;
                fileNameSet[index] = fileName;
                XMLDoc2 = new XMLDoc(doc2, docID);

                /* 计算相似度 */
                result[index++] = this.calcSimilarityList.get(method)
                        .calcSimilarity(XMLDoc1, XMLDoc2);
            }

            /*********************** 排序找到最近的K个返回 ***********************/
            int[] assistArr = new int[result.length];
            for (int i = 0; i < assistArr.length; i++)
            {
                assistArr[i] = i;
            }
            this.sort(result, assistArr, 0, result.length - 1);
            for (int KNN = 0; KNN < this.K; KNN++)
            {
                int pos = assistArr[(assistArr.length - 1) - KNN];
                docMap.put(fileNameSet[pos], docSet[pos]);
            }

            return docMap;
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }

        return docMap;
    }

    /**
     * 用于对similarity result结果进行排序的快速排序方法
     * 
     * @param arr
     *            包含similarity result值的原始数组
     * @param assistArr
     *            用于协助记录特征词index参与排序的辅助数组
     * @param start
     *            待排序数组的起始元素下标
     * @param end
     *            待排序数组的最后一个元素下标
     */
    private void sort(double[] arr, int[] assistArr, int start, int end)
    {
        if (start < end)
        {
            int mid = partition(arr, assistArr, start, end);
            if (this.K >= mid)
            {
                sort(arr, assistArr, start, mid - 1);
                sort(arr, assistArr, mid + 1, end);
            }
            else
            {
                sort(arr, assistArr, start, mid - 1);
            }
        }
    }

    /**
     * 用于协助快排的partition函数
     * 
     * @param arr
     *            包含similarity result值的原始数组
     * @param assistArr
     *            用于协助记录特征词index参与排序的辅助数组
     * @param start
     *            待排序数组的起始元素下标
     * @param end
     *            待排序数组的最后一个元素下标
     */
    private int partition(double[] arr, int[] assistArr, int start, int end)
    {
        // 找到end + start / 2位置对应于assistArr辅助数组的词的index，
        // 然后通过该index在原始result数组中找到该词的similarity值用于排序
        double pivot = arr[assistArr[(end + start) / 2]];

        // 交换中间元素和数组最后一个元素
        int tmpIndex = assistArr[end];
        assistArr[end] = assistArr[(end + start) / 2];
        assistArr[(end + start) / 2] = tmpIndex;

        int small = start - 1;
        for (int big = start; big < end; big++)
        {
            if (arr[assistArr[big]] <= pivot)
            {
                small++;
                int tmp = assistArr[big];
                assistArr[big] = assistArr[small];
                assistArr[small] = tmp;
            }
        }
        int tmp = assistArr[small + 1];
        assistArr[small + 1] = assistArr[end];
        assistArr[end] = tmp;

        return small + 1;
    }

    private File[] loadFiles(String filePath)
    {
        DirWrapper dirWrapper = DirWrapper.getInstance();
        File[] files = dirWrapper.listFiles(filePath);

        if (files == null)
        {
            System.err.println("Error occur in loadFiles of SearchSimiXMLDoc");
            return null;
        }
        else
        {
            return files;
        }
    }

    public static void main(String[] args) throws DocumentException
    {
        SearchSimiXMLDoc searcher = SearchSimiXMLDoc.getInstance();

        // TODO:TEST
        File file = new File(FILE_PATH + "2112299_2433035.xml");
        HashMap<String, Document> docs = searcher.search(file,
                SearchSimiXMLDoc.VSM);
        Set<String> keySet = docs.keySet();
        for (String key : keySet)
        {
            System.out.println(key);
        }
    }
}
